package com.settlement.system.parse.formula;

import java.util.ArrayList;
import java.util.List;

/**
 * 表达式操作的一般计算
 *
 * @param <S> 返回类型
 * @author Ximi
 * @see NumberCalculator
 * @see DateCalculator
 * @since 2020/10/12
 */
public abstract class AbstractOperatorCalculator<S> implements Calculator<S> {

    private PlaceHoldParse placeHoldParse;

    public AbstractOperatorCalculator(PlaceHoldParse placeHoldParse) {
        this.placeHoldParse = placeHoldParse;
    }

    @Override
    public S calculate(String formula) {
        formula = format(formula);
        return nestedCalculate(formula);
    }

    /**
     * 嵌套式计算
     *
     * @param formula
     * @return
     */
    private S nestedCalculate(String formula) {

        List<S> operationalNums = new ArrayList<>();
        List<String> operators = new ArrayList<>();

        StringBuilder optNum = new StringBuilder();
        int rightBracketNumber = 0;
        for (int index = 0; index < formula.length(); index++) {

            if ('(' == formula.charAt(index)) {
                rightBracketNumber++;
                if (rightBracketNumber != 1) {
                    optNum.append(formula.charAt(index));
                }

                continue;
            }

            if (')' == formula.charAt(index)) {
                rightBracketNumber--;
                if (rightBracketNumber == 0) {
                    operationalNums.add(nestedCalculate(optNum.toString()));
                    optNum.setLength(0);
                } else if (rightBracketNumber < 0) {
                    throw new FormulaException("bracket no match");
                } else {
                    optNum.append(formula.charAt(index));
                }

                continue;
            }

            if (index == 0 && '-' == formula.charAt(index)) {
                optNum.append(formula.charAt(index));
                continue;
            }

            char c = formula.charAt(index);
            boolean flag = ('+' == c || '-' == c || '*' == c || '/' == c) && rightBracketNumber == 0;
            if (flag) {
                if (optNum.length() != 0) {
                    operationalNums.add(canverOrParse(optNum.toString()));
                    optNum.setLength(0);
                }
                operators.add(String.valueOf(c));
                continue;
            }

            optNum.append(c);
        }

        if (rightBracketNumber != 0) {
            throw new FormulaException("right bracket number > left bracket number");
        }

        if (optNum.length() != 0) {
            operationalNums.add(canverOrParse(optNum.toString()));
            optNum.setLength(0);
        }

        if (operationalNums.size() != operators.size() + 1) {
            throw new FormulaException("formula Exception");
        }

        return doCalculate(operationalNums, operators);
    }

    /**
     * 最后计算
     *
     * @param operationalNums 操作数
     * @param operators       操作符
     * @return
     */
    private S doCalculate(List<S> operationalNums, List<String> operators) {

        List<S> tagOperationalNums = new ArrayList<>();
        List<String> tagOperators = new ArrayList<>();

        S arg1 = operationalNums.get(0);
        for (int index = 0; index < operators.size(); index++) {
            if ("*".equals(operators.get(index))) {
                arg1 = multiply(arg1, operationalNums.get(index + 1));
            } else if ("/".equals(operators.get(index))) {
                arg1 = devide(arg1, operationalNums.get(index + 1));
            } else {
                tagOperationalNums.add(arg1);
                tagOperators.add(operators.get(index));
                arg1 = operationalNums.get(index + 1);
            }
        }

        tagOperationalNums.add(arg1);

        arg1 = tagOperationalNums.get(0);
        for (int index = 0; index < tagOperators.size(); index++) {
            if ("+".equals(tagOperators.get(index))) {
                arg1 = addition(arg1, tagOperationalNums.get(index + 1));
            } else if ("-".equals(operators.get(index))) {
                arg1 = substract(arg1, tagOperationalNums.get(index + 1));
            }
        }

        return arg1;
    }

    /**
     * 对表达式进行格式化
     *
     * @param formula
     * @return
     */
    private String format(String formula) {
        return formula.replace(" ", "")
                .replace("+-", "-")
                .replace("--", "+");
    }

    /**
     * 转化占位符或者解析
     *
     * @param arg
     * @return
     */
    private S canverOrParse(String arg) {
        if (placeHoldParse.isPlaceHold(arg)) {
            return (S) placeHoldParse.getPlaceHoldValue(arg);
        }
        return conver(arg);
    }

    /**
     * 加法操作
     *
     * @param a
     * @param b
     * @return
     */
    public abstract S addition(S a, S b);

    /**
     * 减法操作
     *
     * @param a
     * @param b
     * @return
     */
    public abstract S substract(S a, S b);

    /**
     * 乘法操作
     *
     * @param a
     * @param b
     * @return
     */
    public abstract S multiply(S a, S b);

    /**
     * 除法操作
     *
     * @param a
     * @param b
     * @return
     */
    public abstract S devide(S a, S b);

    /**
     * 将字符串转化为目标类型
     *
     * @param arg
     * @return
     */
    public abstract S conver(String arg);

}
